1use crate::copp::clarabel_backend::{ConstraintsClarabel, ObjConsClarabel};
26use crate::copp::copp2::formulation::Copp2Problem;
27use crate::copp::copp2::opt2::ClarabelExpertInfor2nd;
28use crate::copp::copp2::opt2::clarabel_constraints::{
29 clarabel_standard_capacity_topp2, clarabel_standard_constraint_topp2,
30};
31use crate::copp::{ClarabelOptions, CoppObjective, clarabel_to_copp2_solution};
32use crate::diag::{
33 CoppError, DebugVerboser, SilentVerboser, SummaryVerboser, TraceVerboser, Verboser, Verbosity,
34 format_duration_human,
35};
36use crate::robot::robot_core::{Robot, RobotBasic, RobotTorque};
37use clarabel::algebra::CscMatrix;
38use clarabel::solver::SupportedConeT::{NonnegativeConeT, SecondOrderConeT, ZeroConeT};
39use clarabel::solver::{DefaultSolution, DefaultSolver, IPSolver, SupportedConeT};
40use core::f64;
41use itertools::{Itertools, izip};
42use nalgebra::{DMatrix, DVectorView};
43
44#[cfg(any(feature = "c", feature = "cpp", feature = "python", test))]
45use crate::copp::copp2::stable::basic::a_to_b_topp2;
46
47pub fn copp2_socp<'a, M: RobotTorque>(
68 problem: &Copp2Problem<'a, M>,
69 options: &ClarabelOptions,
70) -> Result<Vec<f64>, CoppError> {
71 let (a_profile, solution) = copp2_socp_expert(problem, options)?;
72 a_profile.ok_or_else(|| CoppError::ClarabelSolverStatus("copp2_socp".into(), solution.status))
73}
74
75pub fn copp2_socp_expert<'a, M: RobotTorque>(
106 problem: &Copp2Problem<'a, M>,
107 options: &ClarabelOptions,
108) -> Result<(Option<Vec<f64>>, DefaultSolution<f64>), CoppError> {
109 let result = copp2_socp_expert_with_info(problem, options)?;
110 Ok((result.result, result.solution))
111}
112
113pub fn copp2_socp_expert_with_info<'a, M: RobotTorque>(
123 problem: &Copp2Problem<'a, M>,
124 options: &ClarabelOptions,
125) -> Result<ClarabelExpertInfor2nd, CoppError> {
126 match options.verbosity() {
127 Verbosity::Silent => copp2_socp_core(problem, (options, SilentVerboser)),
128 Verbosity::Summary => copp2_socp_core(problem, (options, SummaryVerboser::new())),
129 Verbosity::Debug => copp2_socp_core(problem, (options, DebugVerboser::new())),
130 Verbosity::Trace => copp2_socp_core(problem, (options, TraceVerboser::new())),
131 }
132}
133
134fn copp2_socp_core<'a, M: RobotTorque>(
146 problem: &Copp2Problem<'a, M>,
147 options_verboser: (&ClarabelOptions, impl Verboser),
148) -> Result<ClarabelExpertInfor2nd, CoppError> {
149 let (options, mut verboser) = options_verboser;
150 let (idx_s_start, idx_s_final) = problem.idx_s_interval;
151 if verboser.is_enabled(Verbosity::Summary) {
152 verboser.record_start_time();
153 crate::verbosity_log!(
154 crate::diag::Verbosity::Summary,
155 "\ncopp2_socp started: {} <= idx_s <= {}, objectives = {}, s_len = {}.",
156 idx_s_start,
157 idx_s_final,
158 problem.objectives.len(),
159 problem.s_len()
160 );
161 }
162 if verboser.is_enabled(Verbosity::Trace) {
163 let settings = options.clarabel_settings();
164 crate::verbosity_log!(
165 crate::diag::Verbosity::Summary,
166 "copp2_socp: options snapshot -> allow(almost={}, max_iter={}, max_time={}, callback_term={}, insufficient_progress={}), tol_gap_rel={}, tol_feas={}, max_iter={}, verbose={}",
167 options.is_allow(clarabel::solver::SolverStatus::AlmostSolved),
168 options.is_allow(clarabel::solver::SolverStatus::MaxIterations),
169 options.is_allow(clarabel::solver::SolverStatus::MaxTime),
170 options.is_allow(clarabel::solver::SolverStatus::CallbackTerminated),
171 options.is_allow(clarabel::solver::SolverStatus::InsufficientProgress),
172 settings.tol_gap_rel,
173 settings.tol_feas,
174 settings.max_iter,
175 settings.verbose
176 );
177 }
178 let n = idx_s_final - idx_s_start;
180 let (cap_val_std, cap_b_std, cap_cone_std) =
184 clarabel_standard_capacity_topp2(&problem.robot.constraints, problem.idx_s_interval);
185 let (cap_val_obj, cap_b_obj, cap_cone_obj, n_vars) =
186 clarabel_objective_capacity_copp2(n, problem.objectives, problem.robot);
187 if verboser.is_enabled(Verbosity::Debug) {
188 crate::verbosity_log!(
189 crate::diag::Verbosity::Summary,
190 "copp2_socp: capacity estimate std(val={cap_val_std}, b={cap_b_std}, cone={cap_cone_std}), obj(val={cap_val_obj}, b={cap_b_obj}, cone={cap_cone_obj}), n_vars={n_vars}."
191 );
192 }
193 let mut row = Vec::<usize>::with_capacity(cap_val_std + cap_val_obj);
196 let mut col = Vec::<usize>::with_capacity(cap_val_std + cap_val_obj);
197 let mut val = Vec::<f64>::with_capacity(cap_val_std + cap_val_obj);
198 let mut b = Vec::<f64>::with_capacity(cap_b_std + cap_b_obj);
199 let mut cones = Vec::<SupportedConeT<f64>>::with_capacity(cap_cone_std + cap_cone_obj);
200 if verboser.is_enabled(Verbosity::Trace) {
201 crate::verbosity_log!(
202 crate::diag::Verbosity::Summary,
203 "copp2_socp: allocated capacities row/col/val/b/cones <= {}/{}/{}/{}/{}",
204 cap_val_std + cap_val_obj,
205 cap_val_std + cap_val_obj,
206 cap_val_std + cap_val_obj,
207 cap_b_std + cap_b_obj,
208 cap_cone_std + cap_cone_obj
209 );
210 }
211 let row_before_std = row.len();
213 let col_before_std = col.len();
214 let val_before_std = val.len();
215 let b_before_std = b.len();
216 let cones_before_std = cones.len();
217 clarabel_standard_constraint_topp2(
218 &problem.as_topp2_problem(),
219 (&mut row, &mut col, &mut val, &mut b, &mut cones),
220 &verboser,
221 );
222 if verboser.is_enabled(Verbosity::Trace) {
223 crate::verbosity_log!(
224 crate::diag::Verbosity::Summary,
225 "copp2_socp: standard-constraints delta row/col/val/b/cones = +{}/+{}/+{}/+{}/+{}",
226 row.len() - row_before_std,
227 col.len() - col_before_std,
228 val.len() - val_before_std,
229 b.len() - b_before_std,
230 cones.len() - cones_before_std
231 );
232 }
233 let row_before_sqrt = row.len();
236 let col_before_sqrt = col.len();
237 let val_before_sqrt = val.len();
238 let b_before_sqrt = b.len();
239 let cones_before_sqrt = cones.len();
240 let n_var_old = clarabel_sqrt_a_copp2(
241 n,
242 problem.objectives,
243 problem.a_boundary,
244 (&mut row, &mut col, &mut val, &mut b, &mut cones),
245 );
246 if verboser.is_enabled(Verbosity::Trace) {
247 crate::verbosity_log!(
248 crate::diag::Verbosity::Summary,
249 "copp2_socp: sqrt-a stage delta row/col/val/b/cones = +{}/+{}/+{}/+{}/+{}, n_var_old={}",
250 row.len() - row_before_sqrt,
251 col.len() - col_before_sqrt,
252 val.len() - val_before_sqrt,
253 b.len() - b_before_sqrt,
254 cones.len() - cones_before_sqrt,
255 n_var_old
256 );
257 }
258 let mut q_object = Vec::<f64>::with_capacity(n_vars);
259 q_object.resize(n_var_old, 0.0);
260 let row_before_obj = row.len();
262 let col_before_obj = col.len();
263 let val_before_obj = val.len();
264 let b_before_obj = b.len();
265 let cones_before_obj = cones.len();
266 let q_before_obj = q_object.len();
267 clarable_objective_copp2(
268 problem,
269 (
270 &mut row,
271 &mut col,
272 &mut val,
273 &mut b,
274 &mut cones,
275 &mut q_object,
276 ),
277 )?;
278 if verboser.is_enabled(Verbosity::Trace) {
279 let (q_min, q_max) = q_object
280 .iter()
281 .fold((f64::INFINITY, f64::NEG_INFINITY), |(mn, mx), &v| {
282 (mn.min(v), mx.max(v))
283 });
284 crate::verbosity_log!(
285 crate::diag::Verbosity::Summary,
286 "copp2_socp: objective stage delta row/col/val/b/cones/q = +{}/+{}/+{}/+{}/+{}/+{}, q_range=[{}, {}]",
287 row.len() - row_before_obj,
288 col.len() - col_before_obj,
289 val.len() - val_before_obj,
290 b.len() - b_before_obj,
291 cones.len() - cones_before_obj,
292 q_object.len() - q_before_obj,
293 q_min,
294 q_max
295 );
296 }
297 if verboser.is_enabled(Verbosity::Debug) {
298 crate::verbosity_log!(
299 crate::diag::Verbosity::Summary,
300 "copp2_socp: after objective assembly row={}, col={}, val={}, b={}, cones={}, q={}",
301 row.len(),
302 col.len(),
303 val.len(),
304 b.len(),
305 cones.len(),
306 q_object.len()
307 );
308 }
309 if verboser.is_enabled(Verbosity::Summary) {
310 crate::verbosity_log!(
311 crate::diag::Verbosity::Summary,
312 "copp2_socp: ready to solve with row/col/val/b/cones = {}/{}/{}/{}/{} and n_var = {}.",
313 row.len(),
314 col.len(),
315 val.len(),
316 b.len(),
317 cones.len(),
318 q_object.len()
319 );
320 }
321 let n_var = q_object.len();
323 let a_csc = CscMatrix::new_from_triplets(b.len(), n_var, row, col, val);
324 let p_object = CscMatrix::<f64>::zeros((n_var, n_var));
325 if verboser.is_enabled(Verbosity::Trace) {
326 crate::verbosity_log!(
327 crate::diag::Verbosity::Summary,
328 "copp2_socp: matrix built with m={}, n={}, A.nnz={}, P.nnz={}",
329 b.len(),
330 n_var,
331 a_csc.nnz(),
332 p_object.nnz()
333 );
334 }
335 let settings = options.clarabel_settings().clone();
337 let mut solver = DefaultSolver::<f64>::new(&p_object, &q_object, &a_csc, &b, &cones, settings)
338 .map_err(|e| CoppError::ClarabelSolverError("copp2_socp".into(), e))?;
339 solver.solve();
340 let linsolver = solver.info.linsolver.clone();
341 let solution = solver.solution;
342 if verboser.is_enabled(Verbosity::Summary) {
343 crate::verbosity_log!(
344 crate::diag::Verbosity::Summary,
345 "copp2_socp: solve done, status = {:?}, elapsed = {}.",
346 solution.status,
347 format_duration_human(verboser.elapsed())
348 );
349 }
350 if verboser.is_enabled(Verbosity::Trace) {
351 let show = solution.x.len().min(3);
352 crate::verbosity_log!(
353 crate::diag::Verbosity::Summary,
354 "copp2_socp: solution x_len={}, head={:?}",
355 solution.x.len(),
356 &solution.x[0..show]
357 );
358 }
359 let a_profile = if options.is_allow(solution.status) {
360 Some(clarabel_to_copp2_solution(problem.s_len(), &solution))
361 } else {
362 None
363 };
364 if verboser.is_enabled(Verbosity::Trace) {
365 crate::verbosity_log!(
366 crate::diag::Verbosity::Summary,
367 "copp2_socp: allow(status)={}, extracted_profile={}",
368 options.is_allow(solution.status),
369 if a_profile.is_some() {
370 "Some(a)"
371 } else {
372 "None"
373 }
374 );
375 }
376 Ok(ClarabelExpertInfor2nd {
377 result: a_profile,
378 solution,
379 linsolver,
380 })
381}
382
383fn clarabel_objective_capacity_copp2<M: RobotBasic>(
385 n: usize,
386 objective: &[CoppObjective],
387 robot: &Robot<M>,
388) -> (usize, usize, usize, usize) {
389 let flag_need_eta = objective.iter().any(|obj| {
390 matches!(
391 obj,
392 CoppObjective::Time(_) | CoppObjective::ThermalEnergy(_, _)
393 )
394 });
395 let (mut capacity_val, mut capacity_b, mut capacity_cones, mut n_vars) = if flag_need_eta {
398 (4 * (n + 1), 4 * (n + 1), n + 2, 2 * (n + 1))
399 } else {
400 (0, 0, 0, n + 1)
401 };
402 let dim = robot.dim();
404 for obj in objective {
405 match obj {
406 CoppObjective::Time(_) => {
407 capacity_val += 6 * n;
409 capacity_b += 3 * n;
410 capacity_cones += n;
411 n_vars += n;
412 }
413 CoppObjective::ThermalEnergy(_, _) => {
414 capacity_val += (6 + 2 * dim) * n;
416 capacity_b += (dim + 2) * n;
417 capacity_cones += n;
418 n_vars += n;
419 }
420 CoppObjective::TotalVariationTorque(_, _) => {
421 capacity_val += 8 * dim * n;
423 capacity_b += 2 * dim * n;
424 capacity_cones += 1;
425 n_vars += dim * n;
426 }
427 _ => {}
428 }
429 }
430 (capacity_val, capacity_b, capacity_cones, n_vars)
431}
432
433fn clarabel_sqrt_a_copp2(
448 n: usize,
449 objective: &[CoppObjective],
450 a_boundary: (f64, f64),
451 constraints: ConstraintsClarabel,
452) -> usize {
453 let (row, col, val, b, cones) = constraints;
454 for obj in objective {
455 match obj {
456 CoppObjective::Time(_) | CoppObjective::ThermalEnergy(_, _) => {
457 row.extend(b.len()..b.len() + 2);
460 col.extend([n + 1, 2 * n + 1]);
461 val.extend([1.0, 1.0]);
462 b.extend([a_boundary.0.sqrt(), a_boundary.1.sqrt()]);
463 cones.push(ZeroConeT(2));
464 row.extend(b.len()..b.len() + n - 1);
467 col.extend((n + 2)..(2 * n + 1));
468 val.resize(val.len() + n - 1, -1.0);
469 b.resize(b.len() + n - 1, 0.0);
470 cones.push(NonnegativeConeT(n - 1));
471 row.extend(b.len()..b.len() + 3 * (n - 1));
476 val.resize(val.len() + 3 * (n - 1), -1.0);
477 cones.resize(cones.len() + n - 1, SecondOrderConeT(3));
478 for k in 1..n {
479 col.extend([k, k, k + n + 1]);
480 b.extend([0.25, -0.25, 0.0]);
481 }
482 return 2 * (n + 1);
483 }
484 _ => {}
485 }
486 }
487 n + 1
488}
489
490fn clarabel_objective_time_copp2(
493 s: &[f64],
494 weight: f64,
495 objective_constraints: ObjConsClarabel,
496) -> bool {
497 if weight < 0.0 {
498 return false;
499 }
500 let (row, col, val, b, cones, q_object) = objective_constraints;
501 let weight = 8.0 * weight;
505 let n_var_old = q_object.len();
506 q_object.extend(s.windows(2).map(|s_pair| weight * (s_pair[1] - s_pair[0])));
508 let len = s.len();
512 for k in 0..(len - 1) {
513 row.resize(row.len() + 3, b.len());
515 col.extend([len + k, len + k + 1, n_var_old + k]);
516 val.extend([-1.0, -1.0, -1.0]);
517 b.push(0.0);
518 row.resize(row.len() + 3, b.len());
520 col.extend([len + k, len + k + 1, n_var_old + k]);
521 val.extend([-1.0, -1.0, 1.0]);
522 b.push(0.0);
523 b.push(1.0);
525 }
526 cones.resize(cones.len() + len - 1, SecondOrderConeT(3));
527 true
528}
529
530fn clarabel_objective_thermal_energy_copp2(
533 s: &[f64],
534 weight: f64,
535 normalize: &[f64],
536 coeffs_torque: &(DMatrix<f64>, DMatrix<f64>, DMatrix<f64>),
537 objective_constraints: ObjConsClarabel,
538) -> bool {
539 if weight < 0.0 {
540 return false;
541 }
542 let (row, col, val, b, cones, q_object) = objective_constraints;
543 let len = s.len();
547 let mut coeff_a_curr = coeffs_torque.0.clone();
548 let mut coeff_a_next = coeffs_torque.1.clone();
549 let mut coeff_g = coeffs_torque.2.clone();
550 let weight = 8.0 * weight;
552 let n_var_old = q_object.len();
553 q_object.extend(s.windows(2).map(|s_pair| weight * (s_pair[1] - s_pair[0])));
555 let dim = coeff_a_curr.nrows();
557 if normalize.len() != dim {
559 return false;
560 }
561 let normalize = DVectorView::from_slice(normalize, dim);
562 for mut col in coeff_a_curr.column_iter_mut() {
563 col.component_mul_assign(&normalize);
564 }
565 for mut col in coeff_a_next.column_iter_mut() {
566 col.component_mul_assign(&normalize);
567 }
568 for mut col in coeff_g.column_iter_mut() {
569 col.component_mul_assign(&normalize);
570 }
571 for (k, (col_a_curr, col_a_next, col_g)) in izip!(
576 coeff_a_curr.column_iter(),
577 coeff_a_next.column_iter(),
578 coeff_g.column_iter()
579 )
580 .enumerate()
581 {
582 row.resize(row.len() + 3, b.len());
584 col.extend([len + k, len + k + 1, n_var_old + k]);
585 val.extend([-1.0, -1.0, -1.0]);
586 b.push(0.0);
587 row.resize(row.len() + 3, b.len());
589 col.extend([len + k, len + k + 1, n_var_old + k]);
590 val.extend([-1.0, -1.0, 1.0]);
591 b.push(0.0);
592 for (&v_a_curr, &v_a_next, &v_g) in
594 izip!(col_a_curr.iter(), col_a_next.iter(), col_g.iter())
595 {
596 row.resize(row.len() + 2, b.len());
597 col.extend([k, k + 1]);
598 val.extend([v_a_curr, v_a_next]);
599 b.push(v_g);
600 }
601 }
602 cones.resize(cones.len() + len - 1, SecondOrderConeT(dim + 2));
603 true
604}
605
606fn clarabel_objective_tv_torque_copp2(
609 weight: f64,
610 normalize: &[f64],
611 coeffs_torque: &(DMatrix<f64>, DMatrix<f64>, DMatrix<f64>),
612 objective_constraints: ObjConsClarabel,
613) -> bool {
614 if weight < 0.0 {
615 return false;
616 }
617 let (row, col, val, b, cones, q_object) = objective_constraints;
618 let mut coeff_a_curr = coeffs_torque.0.clone();
621 let mut coeff_a_next = coeffs_torque.1.clone();
622 let mut coeff_g = coeffs_torque.2.clone();
623 let dim = coeff_a_curr.nrows();
624 if normalize.len() != dim {
625 return false;
626 }
627 let normalize = DVectorView::from_slice(normalize, dim);
629 for mut col in coeff_a_curr.column_iter_mut() {
630 col.component_mul_assign(&normalize);
631 }
632 for mut col in coeff_a_next.column_iter_mut() {
633 col.component_mul_assign(&normalize);
634 }
635 for mut col in coeff_g.column_iter_mut() {
636 col.component_mul_assign(&normalize);
637 }
638 let n_b_old = b.len();
643 let mut buffer0 = vec![0.0; dim];
646 let mut buffer1 = vec![0.0; dim];
647 for (k, ((col_a_curr, col_b_curr, col_g_curr), (col_a_next, col_b_next, col_g_next))) in izip!(
648 coeff_a_curr.column_iter(),
649 coeff_a_next.column_iter(),
650 coeff_g.column_iter()
651 )
652 .tuple_windows()
653 .enumerate()
654 {
655 buffer0.clear();
657 buffer1.clear();
658 buffer0.extend(
659 col_a_next
660 .iter()
661 .zip(col_b_curr.iter())
662 .map(|(&v_a_next, &v_b_curr)| v_b_curr - v_a_next),
663 );
664 buffer1.extend(
665 col_g_curr
666 .iter()
667 .zip(col_g_next.iter())
668 .map(|(&v_g_curr, &v_g_next)| v_g_curr - v_g_next),
669 );
670 let n_var_old = q_object.len();
673 for (i, (&v0, &v1, &v_a_curr, &v_b_next)) in izip!(
674 buffer0.iter(),
675 buffer1.iter(),
676 col_a_curr.iter(),
677 col_b_next.iter()
678 )
679 .enumerate()
680 {
681 row.resize(row.len() + 4, b.len());
684 col.extend([k, k + 1, k + 2, n_var_old + i]);
685 val.extend([-v_a_curr, v0, v_b_next, -1.0]);
686 b.push(v1);
687
688 row.resize(row.len() + 4, b.len());
691 col.extend([k, k + 1, k + 2, n_var_old + i]);
692 val.extend([v_a_curr, -v0, -v_b_next, -1.0]);
693 b.push(-v1);
694 }
695
696 q_object.resize(q_object.len() + dim, weight);
698 }
699 cones.push(NonnegativeConeT(b.len() - n_b_old));
700 true
701}
702
703fn clarabel_objective_linear_copp2(
705 s: &[f64],
706 weight: f64,
707 alpha: &[f64],
708 beta: &[f64],
709 q_object: &mut [f64],
710) -> bool {
711 if alpha.len() != s.len() || beta.len() != s.len() - 1 {
712 return false;
713 }
714 for (va, q) in alpha.iter().zip(q_object.iter_mut()) {
717 *q += weight * va;
719 }
720 for (s_pair, vb, q_curr) in izip!(s.windows(2), beta.iter(), q_object.iter_mut()) {
721 *q_curr -= 0.5 * weight * vb / (s_pair[1] - s_pair[0]);
723 }
724 for (s_pair, vb, q_next) in izip!(s.windows(2), beta.iter(), q_object.iter_mut().skip(1)) {
725 *q_next += 0.5 * weight * vb / (s_pair[1] - s_pair[0]);
727 }
728 true
729}
730
731fn clarable_objective_copp2<M: RobotTorque>(
732 problem: &Copp2Problem<M>,
733 objective_constraints: ObjConsClarabel,
734) -> Result<(), CoppError> {
735 let (row, col, val, b, cones, q_object) = objective_constraints;
736 let s = problem
737 .robot
738 .constraints
739 .s_vec(problem.idx_s_interval.0, problem.idx_s_interval.1 + 1)?;
740 let coeffs_torque = if problem.objectives.iter().any(|obj| {
741 matches!(
742 obj,
743 CoppObjective::ThermalEnergy(_, _) | CoppObjective::TotalVariationTorque(_, _)
744 )
745 }) {
746 problem.robot.torque2_coeff_a(
748 problem.idx_s_interval.0,
749 problem.idx_s_interval.1 - problem.idx_s_interval.0,
750 )?
751 } else {
752 (
753 DMatrix::<f64>::zeros(0, 0),
754 DMatrix::<f64>::zeros(0, 0),
755 DMatrix::<f64>::zeros(0, 0),
756 )
757 };
758 for obj in problem.objectives {
759 match obj {
760 CoppObjective::Time(weight) => {
761 if !clarabel_objective_time_copp2(&s, *weight, (row, col, val, b, cones, q_object))
762 {
763 return Err(CoppError::InvalidInput(
764 "copp2_socp".into(),
765 "Invalid Time objective.".into(),
766 ));
767 }
768 }
769 CoppObjective::ThermalEnergy(weight, normalize) => {
770 if !clarabel_objective_thermal_energy_copp2(
771 &s,
772 *weight,
773 normalize,
774 &coeffs_torque,
775 (row, col, val, b, cones, q_object),
776 ) {
777 return Err(CoppError::InvalidInput(
778 "copp2_socp".into(),
779 "Invalid ThermalEnergy objective.".into(),
780 ));
781 }
782 }
783 CoppObjective::TotalVariationTorque(weight, normalize) => {
784 if !clarabel_objective_tv_torque_copp2(
785 *weight,
786 normalize,
787 &coeffs_torque,
788 (row, col, val, b, cones, q_object),
789 ) {
790 return Err(CoppError::InvalidInput(
791 "copp2_socp".into(),
792 "Invalid TotalVariationTorque objective.".into(),
793 ));
794 }
795 }
796 CoppObjective::Linear(weight, alpha, beta) => {
797 if !clarabel_objective_linear_copp2(&s, *weight, alpha, beta, q_object) {
798 return Err(CoppError::InvalidInput(
799 "copp2_socp".into(),
800 "Invalid Linear objective.".into(),
801 ));
802 }
803 }
804 }
805 }
806 Ok(())
807}
808
809#[cfg(any(feature = "c", feature = "cpp", feature = "python", test))]
811pub(crate) fn objective_value_copp2_opt<M: RobotTorque>(
812 robot: &Robot<M>,
813 start_idx_s: usize,
814 objective: &[CoppObjective],
815 a_profile: &[f64],
816) -> (f64, Vec<f64>) {
817 let Ok(s) = robot
818 .constraints
819 .s_vec(start_idx_s, start_idx_s + a_profile.len())
820 else {
821 return (f64::INFINITY, vec![0.0; objective.len()]);
822 };
823 if a_profile.len() != s.len() {
824 return (f64::INFINITY, vec![0.0; objective.len()]);
825 }
826 let Ok(b_profile) = a_to_b_topp2(&s, a_profile) else {
827 return (f64::INFINITY, vec![0.0; objective.len()]);
828 };
829 let torque = if objective.iter().any(|obj| {
830 matches!(
831 obj,
832 CoppObjective::ThermalEnergy(_, _) | CoppObjective::TotalVariationTorque(_, _)
833 )
834 }) {
835 let torque_result =
836 robot.get_torque_with_ab(&a_profile[0..a_profile.len() - 1], &b_profile, start_idx_s);
837 match torque_result {
838 Ok(torque) => torque,
839 _ => return (f64::INFINITY, vec![0.0; objective.len()]),
840 }
841 } else {
842 DMatrix::<f64>::zeros(0, 0)
843 };
844 let a_sqrt = if objective.iter().any(|obj| {
845 matches!(
846 obj,
847 CoppObjective::Time(_) | CoppObjective::ThermalEnergy(_, _)
848 )
849 }) {
850 a_profile.iter().map(|a| a.sqrt()).collect()
851 } else {
852 Vec::new()
853 };
854 let mut obj_val = Vec::with_capacity(objective.len());
855 let mut obj_val_total = 0.0;
856 for obj in objective {
857 match obj {
858 CoppObjective::Time(weight) => {
859 let obj_here = objective_value_time_copp2(&s, &a_sqrt);
860 obj_val.push(obj_here);
861 obj_val_total += weight * obj_here;
862 }
863 CoppObjective::ThermalEnergy(weight, normalize) => {
864 let obj_here =
865 objective_value_thermal_energy_copp2(&s, &a_sqrt, &torque, normalize);
866 obj_val.push(obj_here);
867 obj_val_total += weight * obj_here;
868 }
869 CoppObjective::TotalVariationTorque(weight, normalize) => {
870 let obj_here = objective_value_tv_torque_copp2(&torque, normalize);
871 obj_val.push(obj_here);
872 obj_val_total += weight * obj_here;
873 }
874 CoppObjective::Linear(weight, alpha, beta) => {
875 let obj_here = objective_value_linear_copp2(&s, a_profile, alpha, beta);
876 obj_val.push(obj_here);
877 obj_val_total += weight * obj_here;
878 }
879 }
880 }
881 (obj_val_total, obj_val)
882}
883
884#[cfg(any(feature = "c", feature = "cpp", feature = "python", test))]
887#[inline(always)]
888fn objective_value_time_copp2(s: &[f64], a_sqrt: &[f64]) -> f64 {
889 let mut objective = 0.0;
891 for (s_pair, a_sqrt_pair) in s.windows(2).zip(a_sqrt.windows(2)) {
892 objective += (s_pair[1] - s_pair[0]) / (a_sqrt_pair[0] + a_sqrt_pair[1]);
893 }
894 2.0 * objective
895}
896
897#[cfg(any(feature = "c", feature = "cpp", feature = "python", test))]
899#[inline(always)]
900fn objective_value_thermal_energy_copp2(
901 s: &[f64],
902 a_sqrt: &[f64],
903 torque: &DMatrix<f64>,
904 normalize: &[f64],
905) -> f64 {
906 let mut objective = 0.0;
908 for (s_pair, a_sqrt_pair, torque_col) in
909 izip!(s.windows(2), a_sqrt.windows(2), torque.column_iter())
910 {
911 let mut sum = 0.0;
912 for (torque, normal) in torque_col.iter().zip(normalize.iter()) {
913 sum += (torque * normal).powi(2);
914 }
915 objective += (s_pair[1] - s_pair[0]) / (a_sqrt_pair[0] + a_sqrt_pair[1]) * sum;
916 }
917 2.0 * objective
918}
919
920#[cfg(any(feature = "c", feature = "cpp", feature = "python", test))]
922#[inline(always)]
923fn objective_value_tv_torque_copp2(torque: &DMatrix<f64>, normalize: &[f64]) -> f64 {
924 let mut objective = 0.0;
926 for (torque_col_curr, torque_col_next) in torque.column_iter().tuple_windows() {
927 for (torque_prev, torque_next, normal) in izip!(
928 torque_col_curr.iter(),
929 torque_col_next.iter(),
930 normalize.iter()
931 ) {
932 objective += (torque_next - torque_prev).abs() * normal;
933 }
934 }
935 objective
936}
937
938#[cfg(any(feature = "c", feature = "cpp", feature = "python", test))]
940#[inline(always)]
941fn objective_value_linear_copp2(s: &[f64], a_profile: &[f64], alpha: &[f64], beta: &[f64]) -> f64 {
942 let mut objective = 0.0;
945 for (a_curr, alpha_curr) in a_profile.iter().zip(alpha.iter()) {
946 objective += a_curr * alpha_curr;
948 }
949 for (a_pair, s_pair, beta_curr) in izip!(a_profile.windows(2), s.windows(2), beta.iter()) {
950 objective += 0.5 * beta_curr * (a_pair[1] - a_pair[0]) / (s_pair[1] - s_pair[0]);
952 }
953 objective
954}
955
956#[cfg(test)]
957mod tests {
958 use super::*;
959 use crate::copp::copp2::stable::basic::{
960 Copp2ProblemBuilder, Topp2ProblemBuilder, s_to_t_topp2,
961 };
962 use crate::copp::copp2::stable::reach_set2::{ReachSet2Options, ReachSet2OptionsBuilder};
963 use crate::copp::copp2::stable::topp2_ra::topp2_ra;
964 use crate::copp::{ClarabelOptions, ClarabelOptionsBuilder};
965 use crate::path::{
966 Path, SplineConfig, add_symmetric_axial_limits_for_test, lissajous_path_for_test,
967 };
968 use crate::robot::demo::Plannar2LinkEnd;
969 use crate::robot::robot_core::Robot;
970 use core::panic;
971 use nalgebra::DMatrix;
972 use std::time::Instant;
973 use std::vec;
974
975 #[test]
976 fn test_copp2_socp_only_time() -> Result<(), CoppError> {
977 run_test_copp2_socp_only_time_repeated(1, false)
978 }
979
980 #[test]
983 #[ignore = "slow"]
984 fn test_copp2_socp_only_time_robust() -> Result<(), CoppError> {
985 run_test_copp2_socp_only_time_repeated(100, true)?;
986 Ok(())
987 }
988
989 #[test]
990 fn test_copp2_socp() -> Result<(), CoppError> {
991 let options_socp = ClarabelOptionsBuilder::new()
992 .allow_almost_solved(true)
993 .build()?;
994 run_test_copp2_socp_once(&options_socp)
995 }
996
997 #[test]
1001 fn test_copp2_socp_linear_respects_amax() -> Result<(), CoppError> {
1002 let n: usize = 6;
1003 let s: Vec<f64> = (0..n).map(|k| k as f64).collect();
1004 let amax = vec![1.0; n];
1005 let mut robot = Robot::with_capacity(1usize, n);
1006 robot.with_s(&s)?;
1007 robot.constraints.with_constraint_1order(&amax, 0)?;
1008
1009 let alpha = vec![-1.0; n];
1011 let beta = vec![0.0; n - 1];
1012 let objectives = [CoppObjective::Linear(1.0, &alpha, &beta)];
1013 let problem =
1014 Copp2ProblemBuilder::new(&robot, (0, n - 1), (1.0, 1.0), &objectives).build()?;
1015 let options = ClarabelOptionsBuilder::new()
1016 .allow_almost_solved(true)
1017 .build()?;
1018 let a = copp2_socp(&problem, &options)?;
1019
1020 for (k, (&a_k, &amax_k)) in a.iter().zip(&amax).enumerate() {
1021 assert!(
1022 a_k <= amax_k + 1e-6,
1023 "a[{k}] = {a_k} exceeds amax[{k}] = {amax_k}"
1024 );
1025 }
1026 Ok(())
1027 }
1028
1029 #[test]
1033 fn test_copp2_socp_zero_boundary_converges() -> Result<(), CoppError> {
1034 use crate::path::{Jet3, sin};
1035 use std::f64::consts::PI;
1036
1037 const DIM: usize = 3;
1038 let n: usize = 1001;
1039 let s: Vec<f64> = (0..n).map(|j| j as f64 / (n - 1) as f64).collect();
1040 let limit_max = [1.0; DIM];
1041 let limit_min = [-1.0; DIM];
1042 let normalize = [1.0; DIM];
1043 let objectives = [
1044 CoppObjective::Time(1.0),
1045 CoppObjective::ThermalEnergy(0.1, &normalize),
1046 ];
1047 let options = ClarabelOptionsBuilder::new()
1048 .allow_almost_solved(true)
1049 .build()?;
1050 for k in 0..4 {
1051 let dphi = k as f64 * 1e-15;
1052 let path = Path::from_parametric(
1053 move |s: Jet3| {
1054 vec![
1055 sin(2.0 * PI * s + dphi),
1056 sin(3.0 * PI * s + (0.3 + dphi)),
1057 sin(5.0 * PI * s + (0.7 + dphi)),
1058 ]
1059 },
1060 0.0,
1061 1.0,
1062 )?;
1063 let mut robot = Robot::with_capacity(DIM, n);
1064 robot
1065 .with_s(s.as_slice())?
1066 .with_q_from_path_2nd(&path, 0, n)?
1067 .with_axial_velocity((limit_max.as_slice(), n), (limit_min.as_slice(), n), 0)?
1068 .with_axial_acceleration((limit_max.as_slice(), n), (limit_min.as_slice(), n), 0)?;
1069 let problem =
1070 Copp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0), &objectives).build()?;
1071 let (a, solution) = copp2_socp_expert(&problem, &options)?;
1072 assert!(
1073 a.is_some() && solution.r_prim <= 1e-6,
1074 "dphi = {dphi:e}: status = {:?}, iterations = {}, r_prim = {:e}",
1075 solution.status,
1076 solution.iterations,
1077 solution.r_prim
1078 );
1079 }
1080 Ok(())
1081 }
1082
1083 #[test]
1084 #[ignore = "bindings"]
1085 fn test_copp2_socp_bindings_parity() -> Result<(), CoppError> {
1086 let dim = 3;
1087 let num_waypoints = 8;
1088 let n: usize = 81;
1089 let pi = std::f64::consts::PI;
1090
1091 let waypoints = DMatrix::<f64>::from_fn(dim, num_waypoints, |axis, j| {
1092 let s = j as f64 / (num_waypoints - 1) as f64;
1093 match axis {
1094 0 => 0.20 * (2.0 * pi * s).sin(),
1095 1 => 0.15 * (1.5 * pi * s).cos(),
1096 2 => 0.10 * s * (1.0 - s),
1097 _ => unreachable!("dimension is fixed to 3"),
1098 }
1099 });
1100 let path = Path::from_waypoints_interpolating(&waypoints, SplineConfig::default())?;
1101 let s = DMatrix::<f64>::from_fn(1, n, |_, j| j as f64 / (n - 1) as f64);
1102
1103 let mut robot = Robot::with_capacity(dim, n);
1104 robot
1105 .with_s(&s.as_view())?
1106 .with_q_from_path_2nd(&path, 0, n)?;
1107 add_symmetric_axial_limits_for_test(&mut robot, 10.0, 50.0, None)?;
1108 let torque_max = vec![1.0e6; dim];
1109 let torque_min = vec![-1.0e6; dim];
1110 robot.with_axial_torque((torque_max.as_slice(), n), (torque_min.as_slice(), n), 0)?;
1111
1112 let normalize = vec![1.0; dim];
1113 let options = ClarabelOptionsBuilder::new()
1114 .allow_almost_solved(true)
1115 .build()?;
1116
1117 let objectives_thermal = [
1118 CoppObjective::Time(1.0),
1119 CoppObjective::ThermalEnergy(1.0, &normalize),
1120 ];
1121 let problem_thermal =
1122 Copp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0), &objectives_thermal)
1123 .build()?;
1124 let a_thermal = copp2_socp(&problem_thermal, &options)?;
1125 let (t_final_thermal, _) = s_to_t_topp2(s.as_slice(), &a_thermal, 0.0)?;
1126
1127 let objectives_tv = [
1128 CoppObjective::Time(1.0),
1129 CoppObjective::TotalVariationTorque(1.0, &normalize),
1130 ];
1131 let problem_tv =
1132 Copp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0), &objectives_tv).build()?;
1133 let a_tv = copp2_socp(&problem_tv, &options)?;
1134
1135 crate::verbosity_log!(
1136 crate::diag::Verbosity::Summary,
1137 "COPP2-SOCP Rust bindings parity test: t_final_thermal={:.17}, thermal.len={}, tv.len={}",
1138 t_final_thermal,
1139 a_thermal.len(),
1140 a_tv.len()
1141 );
1142
1143 Ok(())
1144 }
1145
1146 #[test]
1154 #[ignore = "slow"]
1155 fn test_copp2_socp_robust() -> Result<(), CoppError> {
1156 run_test_copp2_socp_repeated(100, true)?;
1157 Ok(())
1158 }
1159
1160 fn run_test_copp2_socp_repeated(n_exp: usize, flag_print_step: bool) -> Result<(), CoppError> {
1161 let options_socp = ClarabelOptionsBuilder::new()
1162 .allow_almost_solved(true)
1163 .build()?;
1164
1165 let mut tc_sum_case0 = 0.0;
1166 let mut tc_sum_case1 = 0.0;
1167 let mut tc_sum_case2 = 0.0;
1168 let mut tc_sum_case3 = 0.0;
1169 let mut tc_sum_case4 = 0.0;
1170 let mut obj_sum_case0 = vec![0.0; 4];
1171 let mut obj_sum_case1 = vec![0.0; 4];
1172 let mut obj_sum_case2 = vec![0.0; 4];
1173 let mut obj_sum_case3 = vec![0.0; 4];
1174 let mut obj_sum_case4 = vec![0.0; 4];
1175
1176 for i_exp in 0..n_exp {
1177 let n: usize = 1000;
1178 let mut robot = Robot::with_capacity(Plannar2LinkEnd::new(1.0, 1.0, 1.0, 1.0), n);
1179 let dim = robot.dim();
1180
1181 let mut rng = rand::rng();
1182 let (s, path, omega, phi) =
1183 lissajous_path_for_test(dim, n, &mut rng).expect("random range is valid");
1184 robot
1185 .with_s(&s.as_view())?
1186 .with_q_from_path_2nd(&path, 0, n)?;
1187 add_symmetric_axial_limits_for_test(&mut robot, 1.0, 1.0, None)?;
1188
1189 let objectives_test = [
1191 CoppObjective::Time(1.0),
1192 CoppObjective::ThermalEnergy(1.0, &vec![1.0; dim]),
1193 CoppObjective::TotalVariationTorque(1.0, &vec![1.0; dim]),
1194 CoppObjective::Linear(1.0, &vec![0.0; n], &vec![1.0; n - 1]),
1195 ];
1196 let a_feasible = vec![0.0; n];
1197
1198 let mut copp2_problem = Copp2ProblemBuilder::new(
1200 &robot,
1201 (0, n - 1),
1202 (0.0, 0.0),
1203 &[CoppObjective::Time(1.0)],
1204 )
1205 .build()?;
1206 let start = Instant::now();
1207 let mut a_case0 = copp2_socp(&copp2_problem, &options_socp)?;
1208 let tc_copp2_case0 = start.elapsed().as_secs_f64() * 1E3;
1209 robot
1210 .constraints
1211 .project_to_feasible_topp2(&mut a_case0, &a_feasible, 0)?;
1212 let (_, obj_case0) = objective_value_copp2_opt(&robot, 0, &objectives_test, &a_case0);
1213
1214 let obj_case1 = [
1216 CoppObjective::Time(1.0),
1217 CoppObjective::ThermalEnergy(1.0, &vec![1.0; dim]),
1218 ];
1219 copp2_problem.objectives = &obj_case1;
1220 let start = Instant::now();
1221 let mut a_case1 = copp2_socp(&copp2_problem, &options_socp)?;
1222 let tc_copp2_case1 = start.elapsed().as_secs_f64() * 1E3;
1223 robot
1224 .constraints
1225 .project_to_feasible_topp2(&mut a_case1, &a_feasible, 0)?;
1226 let (_, obj_case1) = objective_value_copp2_opt(&robot, 0, &objectives_test, &a_case1);
1227 if obj_case1[0] < obj_case0[0] - 1E-3 || obj_case1[1] - 1E-3 > obj_case0[1] {
1228 let (tf_case0, _) = s_to_t_topp2(s.as_slice(), &a_case0, 0.0)?;
1229 let (tf_case1, _) = s_to_t_topp2(s.as_slice(), &a_case1, 0.0)?;
1230 crate::verbosity_log!(
1231 crate::diag::Verbosity::Summary,
1232 "omega = {omega:?}\nphi = {phi:?}"
1233 );
1234 crate::verbosity_log!(
1235 crate::diag::Verbosity::Summary,
1236 "Case 0: obj_time = {:.6}, obj_thermal_energy = {:.6}, tf = {:.6}",
1237 obj_case0[0],
1238 obj_case0[1],
1239 tf_case0
1240 );
1241 crate::verbosity_log!(
1242 crate::diag::Verbosity::Summary,
1243 "Case 1: obj_time = {:.6}, obj_thermal_energy = {:.6}, tf = {:.6}",
1244 obj_case1[0],
1245 obj_case1[1],
1246 tf_case1
1247 );
1248 crate::verbosity_log!(
1249 crate::diag::Verbosity::Summary,
1250 "Interesting... Cases 0 and 1"
1251 );
1252 }
1253
1254 let obj_case2 = [
1256 CoppObjective::Time(1.0),
1257 CoppObjective::ThermalEnergy(10.0, &vec![1.0; dim]),
1258 ];
1259 copp2_problem.objectives = &obj_case2;
1260 let start = Instant::now();
1261 let mut a_case2 = copp2_socp(&copp2_problem, &options_socp)?;
1262 let tc_copp2_case2 = start.elapsed().as_secs_f64() * 1E3;
1263 robot
1264 .constraints
1265 .project_to_feasible_topp2(&mut a_case2, &a_feasible, 0)?;
1266 let (_, obj_case2) = objective_value_copp2_opt(&robot, 0, &objectives_test, &a_case2);
1267 if obj_case2[0] < obj_case1[0] - 1E-3 || obj_case2[1] - 1E-3 > obj_case1[1] {
1268 crate::verbosity_log!(
1269 crate::diag::Verbosity::Summary,
1270 "omega = {omega:?}\nphi = {phi:?}"
1271 );
1272 crate::verbosity_log!(
1273 crate::diag::Verbosity::Summary,
1274 "Case 1: obj_time = {:.6}, obj_thermal_energy = {:.6}",
1275 obj_case1[0],
1276 obj_case1[1]
1277 );
1278 crate::verbosity_log!(
1279 crate::diag::Verbosity::Summary,
1280 "Case 2: obj_time = {:.6}, obj_thermal_energy = {:.6}",
1281 obj_case2[0],
1282 obj_case2[1]
1283 );
1284 crate::verbosity_log!(
1285 crate::diag::Verbosity::Summary,
1286 "Interesting... Cases 1 and 2"
1287 );
1288 }
1289
1290 let obj_case3 = [
1292 CoppObjective::Time(1.0),
1293 CoppObjective::TotalVariationTorque(1.0, &vec![1.0; dim]),
1294 ];
1295 copp2_problem.objectives = &obj_case3;
1296 let start = Instant::now();
1297 let mut a_case3 = copp2_socp(&copp2_problem, &options_socp)?;
1298 let tc_copp2_case3 = start.elapsed().as_secs_f64() * 1E3;
1299 robot
1300 .constraints
1301 .project_to_feasible_topp2(&mut a_case3, &a_feasible, 0)?;
1302 let (_, obj_case3) = objective_value_copp2_opt(&robot, 0, &objectives_test, &a_case3);
1303 if obj_case3[1] < obj_case1[1] - 1E-3 || obj_case3[2] - 1E-3 > obj_case1[2] {
1304 crate::verbosity_log!(
1305 crate::diag::Verbosity::Summary,
1306 "omega = {omega:?}\nphi = {phi:?}"
1307 );
1308 crate::verbosity_log!(
1309 crate::diag::Verbosity::Summary,
1310 "Case 1: obj_time = {:.6}, obj_thermal_energy = {:.6}, obj_total_variation_torque = {:.6}",
1311 obj_case1[0],
1312 obj_case1[1],
1313 obj_case1[2]
1314 );
1315 crate::verbosity_log!(
1316 crate::diag::Verbosity::Summary,
1317 "Case 3: obj_time = {:.6}, obj_thermal_energy = {:.6}, obj_total_variation_torque = {:.6}",
1318 obj_case3[0],
1319 obj_case3[1],
1320 obj_case3[2]
1321 );
1322 crate::verbosity_log!(
1323 crate::diag::Verbosity::Summary,
1324 "Interesting... Cases 1 and 3"
1325 );
1326 }
1327
1328 let obj_case4 = [
1330 CoppObjective::Time(1.0),
1331 CoppObjective::Linear(1.0, &vec![0.0; n], &vec![1.0; n - 1]),
1332 ];
1333 copp2_problem.objectives = &obj_case4;
1334 let start = Instant::now();
1335 let mut a_case4 = copp2_socp(&copp2_problem, &options_socp)?;
1336 let tc_copp2_case4 = start.elapsed().as_secs_f64() * 1E3;
1337 robot
1338 .constraints
1339 .project_to_feasible_topp2(&mut a_case4, &a_feasible, 0)?;
1340 let (_, obj_case4) = objective_value_copp2_opt(&robot, 0, &objectives_test, &a_case4);
1341 if obj_case4[1] < obj_case1[1] - 1E-3 || obj_case4[3] - 1E-3 > obj_case1[3] {
1342 crate::verbosity_log!(
1343 crate::diag::Verbosity::Summary,
1344 "omega = {omega:?}\nphi = {phi:?}"
1345 );
1346 crate::verbosity_log!(
1347 crate::diag::Verbosity::Summary,
1348 "Case 1: obj_time = {:.6}, obj_thermal_energy = {:.6}, obj_linear = {:.6}",
1349 obj_case1[0],
1350 obj_case1[1],
1351 obj_case1[3]
1352 );
1353 crate::verbosity_log!(
1354 crate::diag::Verbosity::Summary,
1355 "Case 4: obj_time = {:.6}, obj_thermal_energy = {:.6}, obj_linear = {:.6}",
1356 obj_case4[0],
1357 obj_case4[1],
1358 obj_case4[3]
1359 );
1360 crate::verbosity_log!(
1361 crate::diag::Verbosity::Summary,
1362 "Interesting... Cases 1 and 4"
1363 );
1364 }
1365 if obj_case4[2] < obj_case2[2] - 1E-3 || obj_case4[3] - 1E-3 > obj_case2[3] {
1366 crate::verbosity_log!(
1367 crate::diag::Verbosity::Summary,
1368 "omega = {omega:?}\nphi = {phi:?}"
1369 );
1370 crate::verbosity_log!(
1371 crate::diag::Verbosity::Summary,
1372 "Case 2: obj_time = {:.6}, obj_total_variation_torque = {:.6}, obj_linear = {:.6}",
1373 obj_case2[0],
1374 obj_case2[2],
1375 obj_case2[3]
1376 );
1377 crate::verbosity_log!(
1378 crate::diag::Verbosity::Summary,
1379 "Case 4: obj_time = {:.6}, obj_total_variation_torque = {:.6}, obj_linear = {:.6}",
1380 obj_case4[0],
1381 obj_case4[2],
1382 obj_case4[3]
1383 );
1384 crate::verbosity_log!(
1385 crate::diag::Verbosity::Summary,
1386 "Interesting... Cases 2 and 4"
1387 );
1388 }
1389
1390 if flag_print_step {
1391 crate::verbosity_log!(
1392 crate::diag::Verbosity::Summary,
1393 "Exp #{}:\n Case 0: tc={:.3}ms, obj={:?}\n Case 1: tc={:.3}ms, obj={:?}\n Case 2: tc={:.3}ms, obj={:?}\n Case 3: tc={:.3}ms, obj={:?}\n Case 4: tc={:.3}ms, obj={:?}",
1394 i_exp + 1,
1395 tc_copp2_case0,
1396 obj_case0,
1397 tc_copp2_case1,
1398 obj_case1,
1399 tc_copp2_case2,
1400 obj_case2,
1401 tc_copp2_case3,
1402 obj_case3,
1403 tc_copp2_case4,
1404 obj_case4
1405 );
1406 }
1407
1408 tc_sum_case0 += tc_copp2_case0;
1409 tc_sum_case1 += tc_copp2_case1;
1410 tc_sum_case2 += tc_copp2_case2;
1411 tc_sum_case3 += tc_copp2_case3;
1412 tc_sum_case4 += tc_copp2_case4;
1413 for i in 0..obj_case0.len() {
1414 obj_sum_case0[i] += obj_case0[i];
1415 obj_sum_case1[i] += obj_case1[i];
1416 obj_sum_case2[i] += obj_case2[i];
1417 obj_sum_case3[i] += obj_case3[i];
1418 obj_sum_case4[i] += obj_case4[i];
1419 }
1420 }
1421
1422 for i in 0..4 {
1423 obj_sum_case0[i] /= n_exp as f64;
1424 obj_sum_case1[i] /= n_exp as f64;
1425 obj_sum_case2[i] /= n_exp as f64;
1426 obj_sum_case3[i] /= n_exp as f64;
1427 obj_sum_case4[i] /= n_exp as f64;
1428 }
1429
1430 crate::verbosity_log!(
1431 crate::diag::Verbosity::Summary,
1432 "Average {} experiments:\n Case 0: tc={:.3}ms, obj={:?}\n Case 1: tc={:.3}ms, obj={:?}\n Case 2: tc={:.3}ms, obj={:?}\n Case 3: tc={:.3}ms, obj={:?}\n Case 4: tc={:.3}ms, obj={:?}",
1433 n_exp,
1434 tc_sum_case0 / n_exp as f64,
1435 obj_sum_case0,
1436 tc_sum_case1 / n_exp as f64,
1437 obj_sum_case1,
1438 tc_sum_case2 / n_exp as f64,
1439 obj_sum_case2,
1440 tc_sum_case3 / n_exp as f64,
1441 obj_sum_case3,
1442 tc_sum_case4 / n_exp as f64,
1443 obj_sum_case4
1444 );
1445
1446 Ok(())
1447 }
1448
1449 fn run_test_copp2_socp_once(_options_socp: &ClarabelOptions) -> Result<(), CoppError> {
1450 run_test_copp2_socp_repeated(1, false)
1451 }
1452
1453 fn run_one_copp2_socp_only_time_case(
1454 options_ra: &ReachSet2Options,
1455 options_socp: &ClarabelOptions,
1456 ) -> Result<(f64, f64, f64, f64, f64, f64), CoppError> {
1457 let n: usize = 1000;
1458 let mut robot = Robot::with_capacity(Plannar2LinkEnd::new(1.0, 1.0, 1.0, 1.0), n);
1459 let dim = robot.dim();
1460
1461 let mut rng = rand::rng();
1462 let (s, path, omega, phi) =
1463 lissajous_path_for_test(dim, n, &mut rng).expect("random range is valid");
1464 robot
1465 .with_s(&s.as_view())?
1466 .with_q_from_path_2nd(&path, 0, n)?;
1467 add_symmetric_axial_limits_for_test(&mut robot, 1.0, 1.0, None)?;
1468
1469 let topp2_problem = Topp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0)).build()?;
1470 let start = Instant::now();
1471 let a_ra = topp2_ra(&topp2_problem, options_ra)?;
1472 let tc_ra = start.elapsed().as_secs_f64() * 1E3;
1473 let (tf_ra, _) = s_to_t_topp2(s.as_slice(), &a_ra, 0.0)?;
1474
1475 let obj1 = [CoppObjective::Linear(
1476 1.0,
1477 &vec![-1.0; n],
1478 &vec![0.0; n - 1],
1479 )];
1480 let mut copp2_problem =
1481 Copp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0), &obj1).build()?;
1482 let start = Instant::now();
1483 let a_lp = copp2_socp(&copp2_problem, options_socp)?;
1484 let tc_lp = start.elapsed().as_secs_f64() * 1E3;
1485 let (tf_lp, _) = s_to_t_topp2(s.as_slice(), &a_lp, 0.0)?;
1486
1487 copp2_problem.objectives = &[CoppObjective::Time(1.0)];
1488 let start = Instant::now();
1489 let a_qp = copp2_socp(&copp2_problem, options_socp)?;
1490 let tc_qp = start.elapsed().as_secs_f64() * 1E3;
1491 let (tf_qp, _) = s_to_t_topp2(s.as_slice(), &a_qp, 0.0)?;
1492
1493 if (tf_lp - tf_ra).abs() > 1e-3 || (tf_qp - tf_ra).abs() > 1e-3 {
1494 crate::verbosity_log!(
1495 crate::diag::Verbosity::Summary,
1496 "omega = {omega:?}\nphi = {phi:?}"
1497 );
1498 panic!("COPP2 time optimality failed!");
1499 }
1500
1501 Ok((tc_ra, tc_lp, tc_qp, tf_ra, tf_lp, tf_qp))
1502 }
1503
1504 fn run_test_copp2_socp_only_time_repeated(
1505 n_exp: usize,
1506 flag_print_step: bool,
1507 ) -> Result<(), CoppError> {
1508 let mut tc_sum_ra = 0.0;
1509 let mut tc_sum_lp = 0.0;
1510 let mut tc_sum_qp = 0.0;
1511 let mut tf_sum_ra = 0.0;
1512 let mut tf_sum_lp = 0.0;
1513 let mut tf_sum_qp = 0.0;
1514
1515 let options_ra = ReachSet2OptionsBuilder::new()
1516 .lp_feas_tol(1E-9)
1517 .a_cmp_abs_tol(1E-9)
1518 .a_cmp_rel_tol(1E-9)
1519 .build()?;
1520 let options_socp = ClarabelOptionsBuilder::new()
1521 .allow_almost_solved(true)
1522 .build()?;
1523
1524 for i_exp in 0..n_exp {
1525 let (tc_ra, tc_lp, tc_qp, tf_ra, tf_lp, tf_qp) =
1526 run_one_copp2_socp_only_time_case(&options_ra, &options_socp)?;
1527
1528 if flag_print_step {
1529 crate::verbosity_log!(
1530 crate::diag::Verbosity::Summary,
1531 "Exp #{}: tc_ra = {:.4} ms, tc_lp = {:.4} ms, tc_qp = {:.4} ms, tf_ra = {:.6}, tf_lp = {:.6}, tf_qp = {:.6}",
1532 i_exp + 1,
1533 tc_ra,
1534 tc_lp,
1535 tc_qp,
1536 tf_ra,
1537 tf_lp,
1538 tf_qp,
1539 );
1540 }
1541
1542 tc_sum_ra += tc_ra;
1543 tc_sum_lp += tc_lp;
1544 tc_sum_qp += tc_qp;
1545 tf_sum_ra += tf_ra;
1546 tf_sum_lp += tf_lp;
1547 tf_sum_qp += tf_qp;
1548 }
1549
1550 crate::verbosity_log!(
1551 crate::diag::Verbosity::Summary,
1552 "Average {} experiments: tc_ra = {:.4} ms, tc_lp = {:.4} ms, tc_qp = {:.4} ms, tf_ra = {:.6}, tf_lp = {:.6}, tf_qp = {:.6}",
1553 n_exp,
1554 tc_sum_ra / n_exp as f64,
1555 tc_sum_lp / n_exp as f64,
1556 tc_sum_qp / n_exp as f64,
1557 tf_sum_ra / n_exp as f64,
1558 tf_sum_lp / n_exp as f64,
1559 tf_sum_qp / n_exp as f64
1560 );
1561
1562 Ok(())
1563 }
1564}